﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DatabaseProjectExtensions.cs" company="Sander van Vliet">
//   2011 Sander van Vliet
// </copyright>
// <summary>
//   The database project extensions.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace TFDP.Common
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;

    using Microsoft.Data.Schema.SchemaModel;
    using Microsoft.VisualStudio.Data.Schema.Package.Project;

    using TFDP.Common.Configuration;

    /// <summary>
    /// The database project extensions.
    /// </summary>
    public static class DatabaseProjectExtensions
    {
        #region Public Methods

        /// <summary>
        /// Find the element specified by entityName in the current model.
        /// </summary>
        /// <typeparam name="T">
        /// The type of the element to find. Must implement IModelElement 
        /// </typeparam>
        /// <param name="project">
        /// The project.
        /// </param>
        /// <param name="entityName">
        /// The fully qualified name of the element 
        /// </param>
        /// <returns>
        /// The specified element or null if the element is not found 
        /// </returns>
        public static T FindEntity<T>(this IDatabaseProjectNode project, string entityName) where T : IModelElement
        {
            ModelIdentifier identifier =
                project.DataSchemaModel.CreateIdentifier(
                    entityName.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries));

            IList<IModelElement> matches = project.DataSchemaModel.GetElements(
                identifier, typeof(T), ModelElementQueryFilter.Internal);

            if (matches == null || matches.Count == 0)
            {
                return default(T);
            }

            if (matches.Count > 1)
            {
                throw new AmbiguousMatchException("Identifier '" + entityName + "' has multiple matches");
            }

            return (T)matches[0];
        }

        /// <summary>
        /// Find the directory in the project where the T4 templates are stored. Start with Templates as the most likely candidate. TODO extend this so we can use some sort of configurationsetting for this.
        /// </summary>
        /// <param name="project">
        /// The project to search in 
        /// </param>
        /// <returns>
        /// String.Empty if no template directory is found, otherwise the full path to the template directory 
        /// </returns>
        public static string FindTemplateDirectory(this IDatabaseProjectNode project)
        {
            string templateDirectory = project.GetProp("TFDP_TemplateDirectory");

            if (string.IsNullOrEmpty(templateDirectory))
            {
                templateDirectory = "Templates";
            }

            IDatabaseNode node = project.FindNode(Path.Combine(project.ProjectFolder, templateDirectory));

            if (node == null)
            {
                return string.Empty;
            }
            
            return node.DirectoryName;
        }

        /// <summary>
        /// The get processor settings.
        /// </summary>
        /// <param name="project">
        /// The project.
        /// </param>
        /// <returns>The settings for the processor</returns>
        public static SettingsCollection GetProcessorSettings(this IDatabaseProjectNode project)
        {
            string propertyValue = project.GetProjectProperty(
                "TFDP_Settings", false, DatabaseProjectPropertyValueType.UnevaluatedValue);
            if (!string.IsNullOrEmpty(propertyValue))
            {
                return SettingsCollection.DeserializeSettings(propertyValue);
            }

            return new SettingsCollection();
        }

        /// <summary>
        /// The get prop.
        /// </summary>
        /// <param name="project">
        /// The project.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <returns>
        /// The property value
        /// </returns>
        public static string GetProp(this IDatabaseProjectNode project, string propertyName)
        {
            return project.GetProjectProperty(propertyName, false, DatabaseProjectPropertyValueType.UnevaluatedValue);
        }

        /// <summary>
        /// Perform a lookup for the current type of element in the list of types. This method supports lookup of interfaces and baseclasses.
        /// </summary>
        /// <param name="element">
        /// The element to find 
        /// </param>
        /// <param name="types">
        /// An IEnumerable&lt;Type&gt; that contains the supported types (class or interface) 
        /// </param>
        /// <returns>
        /// true if element is of a type in the list of types, otherwise false 
        /// </returns>
        public static bool IsMatchingType(this IModelElement element, IEnumerable<Type> types)
        {
            if (types == null)
            {
                throw new ArgumentNullException("types");
            }

            foreach (Type type in types)
            {
                if (type.IsInstanceOfType(element))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Get the item property from the database node instance
        /// </summary>
        /// <param name="node">
        /// The node.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <returns>
        /// The item property instance
        /// </returns>
        public static bool ItemProperty(this IDatabaseNode node, string propertyName)
        {
            string value;

            node.GetItemAttribute(propertyName, out value);

            bool result;

            bool.TryParse(value, out result);

            return result;
        }

        /// <summary>
        /// The node.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="project">The project.</param>
        /// <returns>The database file node instance</returns>
        /// <exception cref="ArgumentNullException">Thrown when element is null</exception>
        /// <exception cref="InvalidOperationException">Thrown when element is not a script sourced element</exception>
        public static IDatabaseFileNode Node(this IModelElement element, IDatabaseProjectNode project)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            if (!(element is IScriptSourcedModelElement))
            {
                throw new InvalidOperationException("element is not ScriptSourced and cannot be mapped to a path");
            }

            if (((IScriptSourcedModelElement)element).PrimarySource != null)
            {
                return project.NodeFromPath(((IScriptSourcedModelElement)element).PrimarySource.SourceName);
            }

            return null;
        }

        /// <summary>
        /// Obtain the IDatabaseFileNode for the given path
        /// </summary>
        /// <param name="project">
        /// The project.
        /// </param>
        /// <param name="path">
        /// The path to locate 
        /// </param>
        /// <returns>
        /// a IDatabaseFileNode if found, otherwise null 
        /// </returns>
        public static IDatabaseFileNode NodeFromPath(this IDatabaseProjectNode project, string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }

            try
            {
                return (IDatabaseFileNode)project.FindNode(path);
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Obtain the IDatabaseFileNode for the given element
        /// </summary>
        /// <param name="project">
        /// The project.
        /// </param>
        /// <param name="element">
        /// The IModelElement to locate 
        /// </param>
        /// <returns>
        /// a IDatabaseFileNode if found, otherwise null 
        /// </returns>
        public static IDatabaseFileNode NodeFromPath(this IDatabaseProjectNode project, IModelElement element)
        {
            return element.Node(project);
        }

        /// <summary>
        /// The set item property.
        /// </summary>
        /// <param name="node">
        /// The node.
        /// </param>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        public static void SetItemProperty(this IDatabaseNode node, string propertyName, string value)
        {
            node.SetItemAttribute(propertyName, value);
        }

        #endregion
    }
}